﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using RibbonLib;
using RibbonLib.Interop;
using pub.Interop;
using pub.NET20Fix;

namespace todo.Ribbon
{
    /// <summary>
    ///   Main class for using the windows ribbon in a .NET application
    /// </summary>
    public class RibbonUI : IUICommandHandler, IUIApplication
    {
        private IUIImageFromBitmap _imageFromBitmap;

        private Dictionary<uint, IRibbonControl> _mapRibbonControls =
                new Dictionary<uint, IRibbonControl>();

        private IUIRibbon UIRibbon;
        private Form ownerForm;

        private Control _ribbonPlaceholder;

        private bool _hidden;

        public event Action OnLoaded;
        public event Action OnInited;

        public void FireOnInited(EventArgs e)
        {
            var handler = OnInited;
            if (handler != null) handler();
        }

        public void FireOnLoaded(EventArgs e)
        {
            var handler = OnLoaded;
            if (handler != null) handler();
        }

        public RibbonUI(Form ownerForm)
        {
            this.ownerForm = ownerForm;
            _ribbonPlaceholder = new RibbonPlaceholder();
            ownerForm.Controls.Add(_ribbonPlaceholder);
        }

        public IUIFramework Framework { get; private set; }

        private void InitFramework(IntPtr hInstance)
        {
            // create ribbon framework object
            Framework = CreateRibbonFramework();
            _imageFromBitmap = CreateImageFromBitmapFactory();

            // init ribbon framework
            var hr = Framework.Initialize(ownerForm.Handle, this);
            if (NativeMethods.Failed(hr))
            {
                Marshal.ThrowExceptionForHR((int) hr);
            }
        }

        /// <summary>
        ///   Destroy ribbon framework
        /// </summary>
        private void DestroyFramework()
        {
            // destroy ribbon framework
            Framework.Destroy();
            Marshal.ReleaseComObject(Framework);

            // remove reference to framework object
            Framework = null;

            if (_imageFromBitmap != null)
            {
                // remove reference to imageFromBitmap object
                _imageFromBitmap = null;
            }

            // remove references to ribbon controls
//            _mapRibbonControls.Clear();
        }

        /// <summary>
        ///   Change ribbon background, highlight and text colors
        /// </summary>
        /// <param name="background"> new background color </param>
        /// <param name="highlight"> new highlight color </param>
        /// <param name="text"> new text color </param>
        public void SetColors(Color background, Color highlight, Color text)
        {
            // convert colors to proper color format
            var backgroundColor =
                    ColorHelper.HSB2Uint(ColorHelper.HSL2HSB(ColorHelper.RGB2HSL(background)));
            var highlightColor =
                    ColorHelper.HSB2Uint(ColorHelper.HSL2HSB(ColorHelper.RGB2HSL(highlight)));
            var textColor = ColorHelper.HSB2Uint(ColorHelper.HSL2HSB(ColorHelper.RGB2HSL(text)));

            var propertyStore = (IPropertyStore) Framework;

            var backgroundColorProp = PropVariant.FromObject(backgroundColor);
            var highlightColorProp = PropVariant.FromObject(highlightColor);
            var textColorProp = PropVariant.FromObject(textColor);

            if (!_defColorRead)
            {
                propertyStore.GetValue(ref RibbonProperties.GlobalBackgroundColor,
                        out defBackgroundColor);
                propertyStore.GetValue(ref RibbonProperties.GlobalHighlightColor,
                        out defHighlightColor);
                propertyStore.GetValue(ref RibbonProperties.GlobalTextColor, out defTextColor);
                _defColorRead = true;
            }

            // set ribbon colors
            propertyStore.SetValue(ref RibbonProperties.GlobalBackgroundColor,
                    ref backgroundColorProp);
            propertyStore.SetValue(ref RibbonProperties.GlobalHighlightColor, ref highlightColorProp);
            propertyStore.SetValue(ref RibbonProperties.GlobalTextColor, ref textColorProp);

            propertyStore.Commit();

            _colorSet = true;
        }

        //
        private bool _colorSet;
        private bool _defColorRead;
        private PropVariant defBackgroundColor;
        private PropVariant defHighlightColor;
        private PropVariant defTextColor;

        public void ResetColors()
        {
            if (!_colorSet)
            {
                return;
            }
            var propertyStore = (IPropertyStore) Framework;
            propertyStore.SetValue(ref RibbonProperties.GlobalBackgroundColor,
                    ref defBackgroundColor);
            propertyStore.SetValue(ref RibbonProperties.GlobalHighlightColor, ref defHighlightColor);
            propertyStore.SetValue(ref RibbonProperties.GlobalTextColor, ref defTextColor);

            propertyStore.Commit();
        }


        /// <summary>
        ///   Wraps a bitmap object with IUIImage interface
        /// </summary>
        /// <param name="bitmap"> bitmap object to wrap </param>
        /// <returns> IUIImage wrapper </returns>
        public IUIImage ConvertToUIImage(Bitmap bitmap)
        {
            if (_imageFromBitmap == null)
            {
                return null;
            }

            IUIImage uiImage;
            _imageFromBitmap.CreateImage(bitmap.GetHbitmap(), Ownership.Transfer, out uiImage);

            return uiImage;
        }

        /// <summary>
        ///   Set current application modes
        /// </summary>
        /// <param name="modesArray"> array of modes to set </param>
        /// <remarks>
        ///   Unlisted modes will be unset
        /// </remarks>
        public void SetModes(params byte[] modesArray)
        {
            // check that ribbon is initialized
            // calculate compact modes value
            var compactModes = 0;
            for (var i = 0; i < modesArray.Length; ++i)
            {
                if (modesArray[i] >= 32)
                {
                    throw new ArgumentException("Modes should range between 0 to 31.");
                }

                compactModes |= (1 << modesArray[i]);
            }

            // set modes
            Framework.SetModes(compactModes);
        }

        /// <summary>
        ///   Shows a predefined context popup in a specific location
        /// </summary>
        /// <param name="contextPopupID"> commandId for the context popup </param>
        /// <param name="x"> X in screen coordinates </param>
        /// <param name="y"> Y in screen coordinates </param>
        public void ShowContextPopup(uint contextPopupID, int x, int y)
        {
            // check that ribbon is initialized

            object contextualUIObject;
            var contextualUIGuid = new Guid(RibbonIIDGuid.IUIContextualUI);
            var hr = Framework.GetView(contextPopupID, ref contextualUIGuid, out contextualUIObject);
            if (NativeMethods.Succeeded(hr))
            {
                var contextualUI = contextualUIObject as IUIContextualUI;
                contextualUI.ShowAtLocation(x, y);
                Marshal.ReleaseComObject(contextualUI);
            }
            else
            {
                Marshal.ThrowExceptionForHR((int) hr);
            }
        }

        /// <summary>
        ///   Specifies whether the ribbon is in a collapsed or expanded state
        /// </summary>
        public bool Minimized
        {
            get
            {
                // check that ribbon is initialized

                var propertyStore = this.UIRibbon as IPropertyStore;
                PropVariant propMinimized;
                var hr = propertyStore.GetValue(ref RibbonProperties.Minimized, out propMinimized);
                return (bool) propMinimized.Value;
            }
            set
            {
                // check that ribbon is initialized
                var propertyStore = this.UIRibbon as IPropertyStore;
                var propMinimized = PropVariant.FromObject(value);
                var hr = propertyStore.SetValue(ref RibbonProperties.Minimized, ref propMinimized);
                hr = propertyStore.Commit();
            }
        }

        /// <summary>
        ///   Specifies whether the ribbon user interface (UI) is in a visible or hidden state
        /// </summary>
        [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public bool Viewable
        {
            get
            {
                // check that ribbon is initialized

                var propertyStore = this.UIRibbon as IPropertyStore;
                PropVariant propViewable;
                var hr = propertyStore.GetValue(ref RibbonProperties.Viewable, out propViewable);

                var result = (bool) propViewable.Value;
                _hidden = !result;
                return result;
            }
            set
            {
                // check that ribbon is initialized
                var propertyStore = this.UIRibbon as IPropertyStore;
                var propViewable = PropVariant.FromObject(value);
                var hr = propertyStore.SetValue(ref RibbonProperties.Viewable, ref propViewable);
                hr = propertyStore.Commit();
                _hidden = !value;
            }
        }

        /// <summary>
        ///   Specifies whether the quick access toolbar is docked at the top or at the bottom
        /// </summary>
        [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public ControlDock QuickAccessToolbarDock
        {
            get
            {
                // check that ribbon is initialized
                var propertyStore = this.UIRibbon as IPropertyStore;
                PropVariant propQuickAccessToolbarDock;
                var hr = propertyStore.GetValue(ref RibbonProperties.QuickAccessToolbarDock,
                        out propQuickAccessToolbarDock);
                return (ControlDock) (uint) propQuickAccessToolbarDock.Value;
            }
            set
            {
                // check that ribbon is initialized

                var propertyStore = this.UIRibbon as IPropertyStore;
                var propQuickAccessToolbarDock = PropVariant.FromObject((uint) value);
                var hr = propertyStore.SetValue(ref RibbonProperties.QuickAccessToolbarDock,
                        ref propQuickAccessToolbarDock);
                hr = propertyStore.Commit();
            }
        }

        public void SaveSettingsToStream(Stream stream)
        {
            var streamAdapter = new StreamAdapter(stream);
            var hr = this.UIRibbon.SaveSettingsToStream(streamAdapter);
        }

        public void LoadSettingsFromStream(Stream stream)
        {
            var streamAdapter = new StreamAdapter(stream);
            var hr = this.UIRibbon.LoadSettingsFromStream(streamAdapter);
        }

        /// <summary>
        ///   Create ribbon framework object
        /// </summary>
        /// <returns> ribbon framework object </returns>
        private static IUIFramework CreateRibbonFramework()
        {
            try
            {
                return new UIRibbonFramework() as IUIFramework;
            }
            catch (COMException exception)
            {
                throw new PlatformNotSupportedException(
                        "The ribbon framework couldn't be found on this system.",
                        exception);
            }
        }

        /// <summary>
        ///   Create image-from-bitmap factory object
        /// </summary>
        /// <returns> image-from-bitmap factory object </returns>
        private static IUIImageFromBitmap CreateImageFromBitmapFactory()
        {
            return new UIRibbonImageFromBitmapFactory() as IUIImageFromBitmap;
        }

        /// <summary>
        ///   Adds a ribbon control to the internal map
        /// </summary>
        /// <param name="ribbonControl"> ribbon control to add </param>
        internal void AddRibbonControl(IRibbonControl ribbonControl)
        {
            _mapRibbonControls.Add(ribbonControl.CommandID, ribbonControl);
        }

        #region Implementation of IUICommandHandler

        /// <summary>
        ///   Implementation of IUICommandHandler.Execute Responds to execute events on Commands bound to the Command handler
        /// </summary>
        /// <param name="commandID"> the command that has been executed </param>
        /// <param name="verb"> the mode of execution </param>
        /// <param name="key"> the property that has changed </param>
        /// <param name="currentValue"> the new value of the property that has changed </param>
        /// <param name="commandExecutionProperties"> additional data for this execution </param>
        /// <returns> Returns S_OK if successful, or an error value otherwise </returns>
        /// <remarks>
        ///   This method is used internally by the Ribbon class and should not be called by the user.
        /// </remarks>
        public virtual HRESULT Execute(uint commandID, ExecutionVerb verb, PropertyKeyRef key,
                                       PropVariantRef currentValue,
                                       IUISimplePropertySet commandExecutionProperties)
        {
#if DEBUG
            Debug.WriteLine(string.Format("Execute verb: {0} for command {1}", verb, commandID));
#endif

            if (_mapRibbonControls.ContainsKey(commandID))
            {
                return _mapRibbonControls[commandID].Execute(verb, key, currentValue,
                        commandExecutionProperties);
            }

            return HRESULT.S_OK;
        }

        /// <summary>
        ///   Implementation of IUICommandHandler.UpdateProperty Responds to property update requests from the Windows Ribbon (Ribbon) framework.
        /// </summary>
        /// <param name="commandID"> The ID for the Command, which is specified in the Markup resource file </param>
        /// <param name="key"> The Property Key to update </param>
        /// <param name="currentValue"> A pointer to the current value for key. This parameter can be NULL </param>
        /// <param name="newValue"> When this method returns, contains a pointer to the new value for key </param>
        /// <returns> Returns S_OK if successful, or an error value otherwise </returns>
        /// <remarks>
        ///   This method is used internally by the Ribbon class and should not be called by the user.
        /// </remarks>
        public virtual HRESULT UpdateProperty(uint commandID, ref PropertyKey key,
                                              PropVariantRef currentValue,
                                              ref PropVariant newValue)
        {
#if DEBUG
//            Debug.WriteLine(string.Format("UpdateProperty key: {0} for command {1}",
//                RibbonProperties.GetPropertyKeyName(ref key), commandID));
#endif

            if (_mapRibbonControls.ContainsKey(commandID))
            {
                return _mapRibbonControls[commandID].UpdateProperty(ref key, currentValue,
                        ref newValue);
            }

            return HRESULT.S_OK;
        }

        #endregion

        public HRESULT OnViewChanged(uint viewId, ViewType typeID, object view, ViewVerb verb,
                                     int uReasonCode)
        {
            var hr = HRESULT.E_FAIL;

            // Checks to see if the view that was changed was a Ribbon view.
            if (typeID == ViewType.Ribbon)
            {
                switch (verb)
                {
                        // The view was newly created
                    case ViewVerb.Create:
                        if (UIRibbon == null)
                        {
                            UIRibbon = view as IUIRibbon;
                        }
                        hr = HRESULT.S_OK;
                        //this.ownerForm.PerformLayout();
                        break;

                        // The view has been resized.  For the Ribbon view, the application should
                        // call GetHeight to determine the height of the ribbon.
                    case ViewVerb.Size:
                        uint uRibbonHeight;
                        // Call to the framework to determine the desired height of the Ribbon.
                        hr = UIRibbon.GetHeight(out uRibbonHeight);

                        if (NativeMethods.Failed(hr))
                        {
                            // error
                        }
                        else
                        {
                            //_ribbonControl.Height = (int)uRibbonHeight;
                            _ribbonPlaceholder.Height = (int) uRibbonHeight;
                            Debug.Print("OnViewChanged, Height: " + uRibbonHeight);
                        }
                        break;

                        // The view was destroyed.
                    case ViewVerb.Destroy:

                        UIRibbon = null;
                        hr = HRESULT.S_OK;
                        break;

                    default:
                        break;
                }
            }

            return hr;
        }

        public HRESULT OnCreateUICommand(uint commandId, CommandType typeID,
                                         out IUICommandHandler commandHandler)
        {
            commandHandler = this;
            return HRESULT.S_OK;
        }

        /// <summary>
        ///   Called for each Command specified in the Windows Ribbon (Ribbon) framework markup when the application window is destroyed.
        /// </summary>
        /// <param name="commandId"> The ID for the Command, which is specified in the markup resource file. </param>
        /// <param name="typeID"> The Command type that is associated with a specific control. </param>
        /// <param name="commandHandler"> A pointer to an IUICommandHandler object. This value can be NULL. </param>
        /// <returns> Returns S_OK if successful, or an error value otherwise. </returns>
        public HRESULT OnDestroyUICommand(uint commandId, CommandType typeID,
                                          IUICommandHandler commandHandler)
        {
            return HRESULT.S_OK;
        }

        public void Load()
        {
//            this._ribbonPlaceholder.Visible = true;
            if (this.ownerForm.Handle == IntPtr.Zero)
            {
                this.ownerForm.HandleCreated += (sender, args) => this.Load();
                return;
            }
//            Win32.SendMessage(this.ownerForm.Handle, Win32.WM_SETREDRAW, 0, 0);

//            this.ownerForm.Controls.SetChildIndex(_ribbonPlaceholder, 0);
//            _ribbonPlaceholder.Visible = true;
            var module = Assembly.GetEntryAssembly().GetModules()[0];
            var hInstance = Marshal.GetHINSTANCE(module);
            InitFramework(hInstance);

            FireOnInited(EventArgs.Empty);
            //
            // load ribbon ui
            var resourceName = "APPLICATION_RIBBON";
            var hr = Framework.LoadUI(hInstance, resourceName);

            if (NativeMethods.Failed(hr))
            {
                Marshal.ThrowExceptionForHR((int) hr);
            }
            //
            if (ownerForm.Visible)
            {
                Win32.SendMessage(this.ownerForm.Handle, Win32.WM_SIZE, 0, 0);
            }
//            Win32.SendMessage(this.ownerForm.Handle, Win32.WM_SETREDRAW, 1, 0);
            Debug.Print("RibbonUI.Load");

            FireOnLoaded(EventArgs.Empty);
        }

        public void Unload()
        {
            _ribbonPlaceholder.Height = 0;
//            ownerForm.BeginInvoke((VoidAction) (() => {
//            Console.WriteLine("??");

            DestroyFramework();


//                callback();
//            }));
        }

        public bool Loaded
        {
            get { return Framework != null; }
        }

        public void Hide()
        {
            Win32.SendMessage(this.ownerForm.Handle, Win32.WM_SETREDRAW, 0, 0);
            this.Viewable = false;
            UpdateHiddenPlaceholderHeight();
            Win32.SendMessage(this.ownerForm.Handle, Win32.WM_SETREDRAW, 1, 0);
            Win32.RedrawWindow(this.ownerForm.Handle, IntPtr.Zero, IntPtr.Zero,
                    Win32.RedrawWindowFlags.Frame |
                    Win32.RedrawWindowFlags.InternalPaint |
                    Win32.RedrawWindowFlags.Invalidate |
                    Win32.RedrawWindowFlags.NoErase |
                    Win32.RedrawWindowFlags.UpdateNow);
        }

        public bool Hidden
        {
            get { return _hidden; }
        }

        public void Show()
        {
            Win32.SendMessage(this.ownerForm.Handle, Win32.WM_SETREDRAW, 0, 0);
            this.Viewable = true;
            Win32.SendMessage(this.ownerForm.Handle, Win32.WM_SETREDRAW, 1, 0);
            Win32.RedrawWindow(this.ownerForm.Handle, IntPtr.Zero, IntPtr.Zero,
                    Win32.RedrawWindowFlags.Frame |
                    Win32.RedrawWindowFlags.InternalPaint |
                    Win32.RedrawWindowFlags.NoErase |
                    Win32.RedrawWindowFlags.UpdateNow);
        }

        public void UpdateHiddenPlaceholderHeight()
        {
            var rect = new Win32.RECT();
            var dwStyle = (uint)Win32.GetWindowLong(this.ownerForm.Handle, Win32.GWL_STYLE);
            var dwExStyle = (uint)Win32.GetWindowLong(this.ownerForm.Handle, Win32.GWL_EXSTYLE);
            Win32.AdjustWindowRectEx(ref rect, dwStyle, false, dwExStyle);
            int height;
            if (rect.Height > 30) // aero?
            {
                height = -rect.top;
                if (this.ownerForm.Menu != null)
                {
                    height += SystemInformation.MenuHeight;
                }
            }
            else
            {
                height = 0;
            }
            _ribbonPlaceholder.Height = height;
        }

        public void NotifyOwnerFormMenuChanged()
        {
            if (this.Hidden)
            {
                UpdateHiddenPlaceholderHeight();
            }
        }
    }
}